#include <stx/imp/cmn.hpp>

inline namespace stx
{
namespace fs
{
Permissions::Permissions(std::string_view const sv_perms)
{
    if (sv_perms.size() != 3)
    {
        RUNTIME_ERROR("fs permissions size must be equal to 3. "sv,
                      sv_perms.size(),
                      _I_,
                      sv_perms);
    }

    for (auto const c : sv_perms)
    {
        if (c < '0' || c > '7')
        {
            RUNTIME_ERROR("Invalid fs permissions: "sv, sv_perms);
        }
    }

    switch (sv_perms[0])
    {
    case '1':
    {
        this->perms_ |= fs::perms::owner_exec;
        break;
    }
    case '2':
    {
        this->perms_ |= fs::perms::owner_write;
        break;
    }
    case '3':
    {
        this->perms_ |= fs::perms::owner_write;
        this->perms_ |= fs::perms::owner_exec;
        break;
    }
    case '4':
    {
        this->perms_ |= fs::perms::owner_read;
        break;
    }
    case '5':
    {
        this->perms_ |= fs::perms::owner_read;
        this->perms_ |= fs::perms::owner_exec;
        break;
    }
    case '6':
    {
        this->perms_ |= fs::perms::owner_read;
        this->perms_ |= fs::perms::owner_write;
        break;
    }
    case '7':
    {
        this->perms_ |= fs::perms::owner_all;
        break;
    }
    default:
        break;
    }

    switch (sv_perms[1])
    {
    case '1':
    {
        this->perms_ |= fs::perms::group_exec;
        break;
    }
    case '2':
    {
        this->perms_ |= fs::perms::group_write;
        break;
    }
    case '3':
    {
        this->perms_ |= fs::perms::group_write;
        this->perms_ |= fs::perms::group_exec;
        break;
    }
    case '4':
    {
        this->perms_ |= fs::perms::group_read;
        break;
    }
    case '5':
    {
        this->perms_ |= fs::perms::group_read;
        this->perms_ |= fs::perms::group_exec;
        break;
    }
    case '6':
    {
        this->perms_ |= fs::perms::group_read;
        this->perms_ |= fs::perms::group_write;
        break;
    }
    case '7':
    {
        this->perms_ |= fs::perms::group_all;
        break;
    }
    default:
        break;
    }

    switch (sv_perms[2])
    {
    case '1':
    {
        this->perms_ |= fs::perms::others_exec;
        break;
    }
    case '2':
    {
        this->perms_ |= fs::perms::others_write;
        break;
    }
    case '3':
    {
        this->perms_ |= fs::perms::others_write;
        this->perms_ |= fs::perms::others_exec;
        break;
    }
    case '4':
    {
        this->perms_ |= fs::perms::others_read;
        break;
    }
    case '5':
    {
        this->perms_ |= fs::perms::others_read;
        this->perms_ |= fs::perms::others_exec;
        break;
    }
    case '6':
    {
        this->perms_ |= fs::perms::others_read;
        this->perms_ |= fs::perms::others_write;
        break;
    }
    case '7':
    {
        this->perms_ |= fs::perms::others_all;
        break;
    }
    default:
        break;
    }
}

Permissions::operator fs::perms() const noexcept
{
    return this->perms_;
}

#define MONITOR_FS_ERR(...)                                                 \
    gLastError().clear();                                                   \
    auto ec = std::error_code{};                                            \
    ON_SCOPE_EXIT(if (ec) {                                                 \
        gLastError().set(ec.value(), ec.message());                         \
        LOG_ERROR(                                                          \
            ec.value(), _I_ __VA_OPT__(, ) __VA_ARGS__, _I_, ec.message()); \
    })

bool isFifo(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_fifo(fp, ec);
}

bool isOther(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_other(fp, ec);
}

bool isSocket(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_socket(fp, ec);
}

bool isBlockFile(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_block_file(fp, ec);
}

bool isCharacterFile(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_character_file(fp, ec);
}

bool isDirectory(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_directory(fp, ec);
}

bool isEmpty(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_empty(fp, ec);
}

bool isRegularFile(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_regular_file(fp, ec);
}

bool isSymlink(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::is_symlink(fp, ec);
}

bool exists(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::exists(fp, ec);
}

fs::path getCurrentPath() noexcept
{
    MONITOR_FS_ERR();
    return std::fs::current_path(ec);
}

fs::path getCanonicalPath(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::canonical(fp, ec);
}

fs::path getWeaklyCanonicalPath(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::weakly_canonical(fp, ec);
}

fs::path readSymlink(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::read_symlink(fp, ec);
}

fs::path getTempDirPath() noexcept
{
    MONITOR_FS_ERR();
    return std::fs::temp_directory_path(ec);
}

fs::path getAbsolutePath(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::absolute(fp, ec);
}

std::size_t getFileSize(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::file_size(fp, ec);
}

std::size_t getHardLinkCount(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::hard_link_count(fp, ec);
}

fs::space_info getSpaceInfo(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::space(fp, ec);
}

fs::perms getFilePerms(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::status(fp, ec).permissions();
}

fs::file_type getFileType(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::status(fp, ec).type();
}

fs::file_status getFileStatus(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::status(fp, ec);
}

bool resizeFile(fs::path const& fp, std::size_t const new_size) noexcept
{
    MONITOR_FS_ERR(fp, _I_, new_size);
    std::fs::resize_file(fp, new_size, ec);
    return gLastError().isGood();
}

bool createDirectory(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::create_directory(fp, ec);
}

bool createDirectories(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::create_directories(fp, ec);
}

bool createSymlink(fs::path const& fp_target, fs::path const& symlink) noexcept
{
    MONITOR_FS_ERR(fp_target, _I_, symlink);
    std::fs::create_symlink(fp_target, symlink, ec);
    return gLastError().isGood();
}

bool copySymlink(fs::path const& from, fs::path const& to) noexcept
{
    MONITOR_FS_ERR(from, _I_, to);
    std::fs::copy_symlink(from, to, ec);
    return gLastError().isGood();
}

bool setCurrentPath(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    std::fs::current_path(fp, ec);
    return gLastError().isGood();
}

bool touch(fs::path const& fp) noexcept
{
    return std::ofstream(fp) && fs::exists(fp);
}

bool rename(fs::path const& fp_old, fs::path const& fp_new) noexcept
{
    MONITOR_FS_ERR(fp_old, _I_, fp_new);
    std::fs::rename(fp_old, fp_new, ec);
    return gLastError().isGood();
}

bool remove(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::remove(fp, ec);
}

std::size_t removeAll(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::remove_all(fp, ec);
}

std::size_t removeSubEntries(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    if (!fs::isDirectory(fp))
    {
        return false;
    }

    auto ps = std::deque<fs::path>{};
    for (auto const& entry : getEntryRange(fp))
    {
        insert(ps, entry.path());
    }

    ranges::for_each(ps, removeAll);
    return gLastError().isGood();
}

fs::space_info space(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return std::fs::space(fp, ec);
}

bool equivalent(fs::path const& fp1, fs::path const& fp2) noexcept
{
    MONITOR_FS_ERR(fp1, _I_, fp2);
    return std::fs::equivalent(fp1, fp2, ec);
}

bool createDirectory(fs::path const& fp_new_dir,
                     fs::path const& fp_existing_dir_to_copy_perms) noexcept
{
    MONITOR_FS_ERR(fp_new_dir, _I_, fp_existing_dir_to_copy_perms);
    return std::fs::create_directory(
        fp_new_dir, fp_existing_dir_to_copy_perms, ec);
}

bool createDirectorySymlink(fs::path const& fp_target,
                            fs::path const& fp_symlink) noexcept
{
    MONITOR_FS_ERR(fp_target, _I_, fp_symlink);
    std::fs::create_directory_symlink(fp_target, fp_symlink, ec);
    return gLastError().isGood();
}

bool createHardLink(fs::path const& fp_target,
                    fs::path const& fp_hard_link) noexcept
{
    MONITOR_FS_ERR(fp_target, _I_, fp_hard_link);
    std::fs::create_hard_link(fp_target, fp_hard_link, ec);
    return gLastError().isGood();
}

bool copy(fs::path const&        fp_from,
          fs::path const&        fp_to,
          fs::copy_options const opts) noexcept
{
    MONITOR_FS_ERR(fp_from, _I_, fp_to, _I_, opts);
    std::fs::copy(fp_from, fp_to, opts, ec);
    return gLastError().isGood();
}

bool copyFile(fs::path const&        fp_from,
              fs::path const&        fp_to,
              fs::copy_options const opts) noexcept
{
    MONITOR_FS_ERR(fp_from, _I_, fp_to, _I_, opts);
    return std::fs::copy_file(fp_from, fp_to, opts, ec);
}

bool setLastWriteTime(fs::path const& fp, SysTimePoint const tp_sys) noexcept
{
    MONITOR_FS_ERR(fp);
    std::fs::last_write_time(fp, SysToFile(tp_sys), ec);
    return gLastError().isGood();
}

SysTimePoint getLastWriteTime(fs::path const& fp) noexcept
{
    MONITOR_FS_ERR(fp);
    return FileToSys(std::fs::last_write_time(fp, ec));
}

fs::path getRelativePath(fs::path const& fp_given,
                         fs::path const& fp_base) noexcept
{
    MONITOR_FS_ERR(fp_given, _I_, fp_base);
    return std::fs::relative(fp_given, fp_base, ec);
}

fs::path getProximatePath(fs::path const& fp_given,
                          fs::path const& fp_base) noexcept
{
    MONITOR_FS_ERR(fp_given, _I_, fp_base);
    return std::fs::proximate(fp_given, fp_base, ec);
}

bool setPermissions(fs::path const&        fp,
                    fs::perms const        pms,
                    fs::perm_options const opts) noexcept
{
    MONITOR_FS_ERR(fp, _I_, pms, _I_, opts);
    std::fs::permissions(fp, pms, opts, ec);
    return gLastError().isGood();
}

bool setPermissionsRecursive(fs::path const&        fp,
                             fs::perms const        pms,
                             fs::perm_options const opts) noexcept
{
    MONITOR_FS_ERR(fp, _I_, ton(pms), _I_, ton(opts));
    if (!setPermissions(fp, pms, opts))
    {
        return false;
    }

    if (fs::isDirectory(fp))
    {
        for (auto const& entry : getEntryRangeRecursive(fp))
        {
            if (!setPermissions(entry.path(), pms, opts))
            {
                return false;
            }
        }
    }

    return true;
}

std::size_t removeEntries__(fs::path const&   fp_dir,
                            std::regex const& re,
                            fs::Filter const  remove_filter,
                            bool const        recursive) noexcept
{
    if (!fs::isDirectory(fp_dir))
    {
        return 0;
    }

    auto entries_to_remove = std::deque<fs::path>{};

    auto const fn_collect = [&](auto const& entry_range)
    {
        for (auto const& entry : entry_range)
        {
            switch (remove_filter)
            {
            case fs::Filter::K_FILE:
            {
                if (entry.is_regular_file() &&
                    isMatch(entry.path().native(), re))
                {
                    insert(entries_to_remove, entry.path());
                }

                continue;
            }
            case fs::Filter::K_DIR:
            {
                if (entry.is_directory() && isMatch(entry.path().native(), re))
                {
                    insert(entries_to_remove, entry.path());
                }

                continue;
            }
            default:
            {
                if (entry.is_regular_file() || entry.is_directory())
                {
                    if (isMatch(entry.path().native(), re))
                    {
                        insert(entries_to_remove, entry.path());
                    }
                }

                continue;
            }
            }
        }
    };

    if (recursive)
    {
        fn_collect(getEntryRangeRecursive(fp_dir));
    }
    else
    {
        fn_collect(getEntryRange(fp_dir));
    }

    for (auto const& entry : entries_to_remove)
    {
        removeAll(entry);
    }

    return entries_to_remove.size();
}

std::size_t removeFiles(fs::path const& fp_dir, std::regex const& re) noexcept
{
    return removeEntries__(fp_dir, re, fs::Filter::K_FILE, false);
}

std::size_t removeDirectories(fs::path const&   fp_dir,
                              std::regex const& re) noexcept
{
    return removeEntries__(fp_dir, re, fs::Filter::K_DIR, false);
}

std::size_t removeEntries(fs::path const& fp_dir, std::regex const& re) noexcept
{
    return removeEntries__(fp_dir, re, fs::Filter::K_BOTH, false);
}

std::size_t removeFilesRecursive(fs::path const&   fp_dir,
                                 std::regex const& re) noexcept
{
    return removeEntries__(fp_dir, re, fs::Filter::K_FILE, true);
}

std::size_t removeDirectoriesRecursive(fs::path const&   fp_dir,
                                       std::regex const& re) noexcept
{
    return removeEntries__(fp_dir, re, fs::Filter::K_DIR, true);
}

std::size_t removeEntriesRecursive(fs::path const&   fp_dir,
                                   std::regex const& re) noexcept
{
    return removeEntries__(fp_dir, re, fs::Filter::K_BOTH, true);
}
} // namespace fs
} // namespace stx